package com.magnificent.atlantis.client.robot.ui.panel;

import com.magnificent.atlantis.client.AtlantisConstants;
import com.magnificent.atlantis.client.data.alert.AlertsContainer;
import com.magnificent.atlantis.client.data.configuration.ConfigurationContainer;
import com.magnificent.atlantis.client.data.configuration.ServerConfiguration;
import com.magnificent.atlantis.client.data.message.MessagesContainer;
import com.magnificent.atlantis.client.data.settings.SettingsContainer;
import com.magnificent.atlantis.client.data.task.planner.AbstractPlannerTask;
import com.magnificent.atlantis.client.event.EventDispatcher;
import com.magnificent.atlantis.client.event.EventDispatcherListener;
import com.magnificent.atlantis.client.event.EventType;
import com.magnificent.atlantis.client.robot.Robot;
import com.magnificent.atlantis.client.robot.RobotContext;
import com.magnificent.atlantis.client.robot.mail.Mailer;
import com.magnificent.atlantis.client.robot.strategies.RobotManagerStrategy;
import com.magnificent.atlantis.client.robot.strategies.RobotStrategy;
import com.magnificent.atlantis.client.robot.strategies.RobotWorkerStrategy;
import com.magnificent.atlantis.client.robot.ui.configure.RobotConfiguration;
import com.magnificent.atlantis.client.scheduler.Scheduler;
import com.magnificent.atlantis.client.scheduler.logger.LoggerEvent;
import com.magnificent.atlantis.client.scheduler.logger.UserThreadLogger;
import com.magnificent.atlantis.client.ui.planner.PlannerData;
import com.magnificent.commons.utils.DateUtils;
import com.magnificent.commons.utils.collections.list.MetaArrayList;
import com.magnificent.commons.utils.collections.list.MetaList;
import com.magnificent.commons.utils.collections.list.functions.PredicateFunction;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.data.gantt.Task;
import org.jfree.data.gantt.TaskSeries;
import org.jfree.data.gantt.TaskSeriesCollection;
import org.jfree.data.time.SimpleTimePeriod;
import org.jfree.data.time.TimePeriod;

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.util.*;

/**
 * User: Babanin
 * Date: Sep 19, 2009
 * Time: 3:28:14 PM
 */
public abstract class AbstractUserTabPanel extends JPanel implements UserThreadLogger.LoggerEventListener, EventDispatcherListener {
    protected PlannerData plannerData;
    protected RobotContext robotContext;
    protected RobotStrategy robotStrategy;
    protected ServerConfiguration serverConfiguration;
    protected RobotConfiguration robotConfiguration;

    protected Robot robot;
    protected Mailer mailer;
    protected Scheduler scheduler;

    private Style infoStyle;
    private Style errorStyle;
    private Style warningStyle;
    protected JTextPane logTextPane;

    protected JSplitPane splitPane;
    protected JFreeChart ganttChart;

    private static final Date START_DATE;
    private static final Date END_DATE;

    static {
        Calendar c = Calendar.getInstance();

        c.set(1970, 1, 1, 0, 0);
        START_DATE = c.getTime();

        c.set(1970, 1, 1, 23, 59);
        END_DATE = c.getTime();
    }

    public AbstractUserTabPanel(RobotConfiguration robotConfiguration) {
        initializeRobot(robotConfiguration);
        initializeControls();
    }

    private void initializeControls() {
        logTextPane = new JTextPane();
        logTextPane.setEditable(false);
        logTextPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));

        splitPane = new JSplitPane();
        splitPane.setOneTouchExpandable(true);

        StyledDocument doc = (StyledDocument) logTextPane.getDocument();

        infoStyle = doc.addStyle("INFO", null);
        warningStyle = doc.addStyle("WARNING", null);
        errorStyle = doc.addStyle("ERROR", null);

        StyleConstants.setForeground(infoStyle, Color.BLUE.darker());
        StyleConstants.setForeground(warningStyle, Color.ORANGE.darker());
        StyleConstants.setForeground(errorStyle, Color.RED.darker());

        ganttChart = ChartFactory.createGanttChart("Schedule", "Day", "Time", getGanttChartTasks(), true, true, false);
    }

    private void initializeRobot(RobotConfiguration robotConfiguration) {
        this.robotConfiguration = robotConfiguration;

        serverConfiguration = new ServerConfiguration();
        serverConfiguration.setHost(robotConfiguration.getHost());
        serverConfiguration.setPort(robotConfiguration.getPort());

        plannerData = createPlannerData(serverConfiguration);

        robotContext = robotConfiguration.toRobotContext();
        robotContext.setPlannerData(plannerData);

        robotStrategy = initializeRobotStrategy(robotContext);

        mailer = new Mailer(robotContext);
        robot = new Robot(robotContext, robotStrategy);
        scheduler = new Scheduler(plannerData, robotStrategy, robotContext);

        EventDispatcher.getInstance(robotContext).addListener(this);
        UserThreadLogger.getInstance(robotContext).addLoggerListener(this);
    }

    public static AbstractUserTabPanel createTab(RobotConfiguration configuration) {
        if (configuration.getStrategy() instanceof RobotManagerStrategy) {
            return new ManagerTabPanel(configuration);
        } else if (configuration.getStrategy() instanceof RobotWorkerStrategy) {
            return new WorkerTabPanel(configuration);
        }

        throw new RuntimeException();
    }

    public abstract void start();

    public abstract void pause();

    public abstract void stop();

    protected abstract RobotStrategy initializeRobotStrategy(RobotContext context);

    public JSplitPane getJSplitPane() {
        return splitPane;
    }

    public void newLogEvent(LoggerEvent event) {
        Document document = logTextPane.getDocument();

        try {
            Style style = infoStyle;

            if (event.getEventType() == LoggerEvent.EventType.WARNING) {
                style = warningStyle;
            } else if (event.getEventType() == LoggerEvent.EventType.ERROR) {
                style = errorStyle;
            }

            document.insertString(document.getLength(), event.getMessage() + "\n", style);
            logTextPane.setCaretPosition(logTextPane.getCaretPosition() + event.getMessage().length());
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    private static PlannerData createPlannerData(ServerConfiguration configuration) {
        final ConfigurationContainer configurationContainer = new ConfigurationContainer();
        configurationContainer.getConfigurations().add(configuration);

        return new PlannerData(new AlertsContainer(), new SettingsContainer(), new MessagesContainer(), configurationContainer);
    }

    private TaskSeriesCollection getGanttChartTasks() {
        TaskSeriesCollection taskSeriesCollection = new TaskSeriesCollection();
        System.out.println(plannerData.getConfigurationContainer().getDataTasks().size());
        MetaList<AbstractPlannerTask> abstractPlannerTasks = new MetaArrayList<AbstractPlannerTask>(plannerData.getConfigurationContainer().getDataTasks());
        abstractPlannerTasks = abstractPlannerTasks.where(new PredicateFunction<AbstractPlannerTask>() {
            public boolean predicate(AbstractPlannerTask abstractPlannerTask) {
                return abstractPlannerTask.isScheduled();
            }
        });

        Map<Date, MetaList<AbstractPlannerTask>> basicTasks = splitTasksByDay(
                abstractPlannerTasks.where(new PredicateFunction<AbstractPlannerTask>() {
                    public boolean predicate(AbstractPlannerTask abstractPlannerTask) {
                        return abstractPlannerTask.isBasicTask();
                    }
                })
        );

        Map<Date, MetaList<AbstractPlannerTask>> meetingTasks = splitTasksByDay(
                abstractPlannerTasks.where(new PredicateFunction<AbstractPlannerTask>() {
                    public boolean predicate(AbstractPlannerTask abstractPlannerTask) {
                        return abstractPlannerTask.isMeetingTask();
                    }
                })
        );

        TaskSeries basicTaskSeries = new TaskSeries("Basic tasks");
        MetaList<Map.Entry<Date, MetaList<AbstractPlannerTask>>> basicTaskEntries =
                new MetaArrayList<Map.Entry<Date, MetaList<AbstractPlannerTask>>>(basicTasks.entrySet()).orderBy(new Comparator<Map.Entry<Date, MetaList<AbstractPlannerTask>>>() {
                    public int compare(Map.Entry<Date, MetaList<AbstractPlannerTask>> o1, Map.Entry<Date, MetaList<AbstractPlannerTask>> o2) {
                        return o1.getKey().compareTo(o2.getKey());
                    }
                });

        for (Map.Entry<Date, MetaList<AbstractPlannerTask>> entry : basicTaskEntries) {
            Task task = new Task(AtlantisConstants.DATE_FORMAT_WITH_DOTS.format(entry.getKey()), START_DATE, END_DATE);

            for (AbstractPlannerTask plannerTask : entry.getValue()) {
                task.addSubtask(new Task("" + plannerTask.getId(), extractTimePeriod(plannerTask)));
            }

            basicTaskSeries.add(task);
        }

        TaskSeries meetingTaskSeries = new TaskSeries("Meeting tasks");
        MetaList<Map.Entry<Date, MetaList<AbstractPlannerTask>>> meetingTaskEntries =
                new MetaArrayList<Map.Entry<Date, MetaList<AbstractPlannerTask>>>(meetingTasks.entrySet()).orderBy(new Comparator<Map.Entry<Date, MetaList<AbstractPlannerTask>>>() {
                    public int compare(Map.Entry<Date, MetaList<AbstractPlannerTask>> o1, Map.Entry<Date, MetaList<AbstractPlannerTask>> o2) {
                        return o1.getKey().compareTo(o2.getKey());
                    }
                });
        for (Map.Entry<Date, MetaList<AbstractPlannerTask>> entry : meetingTaskEntries) {
            Task task = new Task(AtlantisConstants.DATE_FORMAT_WITH_DOTS.format(entry.getKey()), START_DATE, END_DATE);

            for (AbstractPlannerTask plannerTask : entry.getValue()) {
                task.addSubtask(new Task("" + plannerTask.getId(), extractTimePeriod(plannerTask)));
            }

            meetingTaskSeries.add(task);
        }

        taskSeriesCollection.add(basicTaskSeries);
        taskSeriesCollection.add(meetingTaskSeries);

        return taskSeriesCollection;
    }

    private Map<Date, MetaList<AbstractPlannerTask>> splitTasksByDay(MetaList<AbstractPlannerTask> tasks) {
        Map<Date, MetaList<AbstractPlannerTask>> map = new HashMap<Date, MetaList<AbstractPlannerTask>>();

        for (AbstractPlannerTask plannerTask : tasks) {
            Date date = DateUtils.startOfDay(plannerTask.getStartDate());

            if (!map.containsKey(date)) {
                map.put(date, new MetaArrayList<AbstractPlannerTask>());
            }

            map.get(date).add(plannerTask);
        }

        return map;
    }

    private static TimePeriod extractTimePeriod(AbstractPlannerTask plannerTask) {
        Calendar timeExtractor = Calendar.getInstance();

        Calendar startTime = Calendar.getInstance();
        timeExtractor.setTime(plannerTask.getStartDate());
        startTime.set(1970, 1, 1, timeExtractor.get(Calendar.HOUR_OF_DAY), timeExtractor.get(Calendar.MINUTE));

        Calendar endTime = Calendar.getInstance();
        timeExtractor.setTime(plannerTask.getEndDate());
        endTime.set(1970, 1, 1, timeExtractor.get(Calendar.HOUR_OF_DAY), timeExtractor.get(Calendar.MINUTE));

        if ((endTime.getTime().getTime() - startTime.getTime().getTime()) < 0) {
            System.out.println("ERROR task.id=" + plannerTask.getId());
        }

        return new SimpleTimePeriod(startTime.getTime(), endTime.getTime());
    }

    public void newEvent(EventType eventType, Object obj) {
        TaskSeriesCollection categoryDataset = getGanttChartTasks();
        System.out.println("Gantt chart updated = " + ganttChart + " Basic tasks=" + categoryDataset.getColumnCount());
        CategoryPlot plot = (CategoryPlot) ganttChart.getPlot();
        plot.setDataset(categoryDataset);
    }
}
